﻿//
// Copyright (c) 2011 Canyala Innovation AB
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
//

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Canyala.Lagoon.Functional
{
    /// <summary>
    /// Provides a functional factory
    /// </summary>
    public static class Seq
    {
        public static IEnumerable<int> OfInts(int count)
        {
            Contract.Requires<ArgumentException>(count >= 0);

            for (int i=0; i<count; i++)
                yield return i;
        }

        public static IEnumerable<int> OfInts(int from, int to)
        { 
            if (from < to)
                for (int i=from; i<=to; i++) yield return i; 
            else
                for (int i=from; i>=to; i--) yield return i;
        }

        public static IEnumerable<T> One<T>(T one)
        {
            Contract.Requires<ArgumentException>(one != null);

            yield return one;
        }

        public static IEnumerable<T> Two<T>(T one, T two)
        {
            Contract.Requires<ArgumentException>(one != null);
            Contract.Requires<ArgumentException>(two != null);

            yield return one;
            yield return two;
        }

        public static IEnumerable<T> Three<T>(T one, T two, T three)
        {
            Contract.Requires<ArgumentException>(one != null);
            Contract.Requires<ArgumentException>(two != null);
            Contract.Requires<ArgumentException>(three != null);

            yield return one;
            yield return two;
            yield return three;
        }

        public static IEnumerable<T> Many<T>(params T[] arguments)
        {
            return arguments;
        }

        public static IEnumerable<T> Empty<T>()
        {
            yield break;
        }

        public static void Do<T>(IEnumerable<T> seq, Action<T> action)
        {
            Contract.Requires<ArgumentException>(seq != null);
            Contract.Requires<ArgumentException>(action != null);

            foreach (var item in seq)
                action(item);
        }

        public static void Do<TOne,TTwo>(IEnumerable<TOne> seqOne, IEnumerable<TTwo> seqTwo, Action<TOne,TTwo> action)
        {
            Contract.Requires<ArgumentException>(seqOne != null);
            Contract.Requires<ArgumentException>(seqTwo != null);
            Contract.Requires<ArgumentException>(action != null);

            var enumOne = seqOne.GetEnumerator();
            var enumTwo = seqTwo.GetEnumerator();

            while (enumOne.MoveNext() && enumTwo.MoveNext())
                action(enumOne.Current, enumTwo.Current);
        }

        public static bool DoWhile<TOne, TTwo>(IEnumerable<TOne> seqOne, IEnumerable<TTwo> seqTwo, Func<TOne, TTwo, bool> predicateAction)
        {
            Contract.Requires<ArgumentException>(seqOne != null);
            Contract.Requires<ArgumentException>(seqTwo != null);
            Contract.Requires<ArgumentException>(predicateAction != null);

            var enumOne = seqOne.GetEnumerator();
            var enumTwo = seqTwo.GetEnumerator();

            while (true)
            {
                var enumOneState = enumOne.MoveNext();
                var enumTwoState = enumTwo.MoveNext();

                if (enumOneState != enumTwoState)
                    return false;

                if (!enumOneState && !enumTwoState)
                    return true;

                if (!predicateAction(enumOne.Current, enumTwo.Current))
                    return false;
            }
        }

        public static bool DoUntil<TOne, TTwo>(IEnumerable<TOne> seqOne, IEnumerable<TTwo> seqTwo, Func<TOne, TTwo, bool> predicateAction)
        {
            Contract.Requires<ArgumentException>(seqOne != null);
            Contract.Requires<ArgumentException>(seqTwo != null);
            Contract.Requires<ArgumentException>(predicateAction != null);

            var enumOne = seqOne.GetEnumerator();
            var enumTwo = seqTwo.GetEnumerator();

            while (true)
            {
                var oneHasItems = enumOne.MoveNext();
                var twoHasItems = enumTwo.MoveNext();

                if (oneHasItems != twoHasItems)
                    return false;

                if (!oneHasItems && !twoHasItems)
                    return true;

                if (predicateAction(enumOne.Current, enumTwo.Current))
                    return false;
            }
        }

        public static IEnumerable<T> Make<T>(int numberOfItems) where T : new()
        {
            Contract.Requires<ArgumentException>(numberOfItems >= 0);
            Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

            for (int i = 0; i < numberOfItems; i++)
                yield return new T();
        }

        public static T Make<T>() where T : new()
        {
            return Make<T>(1).Single();
        }
    }
}
